//
//  Utility.m
//

#import "Utility.h"

#import "ExifItem.h"
#import "ThumbData.h"

#import "Epeg.h"
#import "exif.h"
#import "jpeg.h"

//------------------------------------------------------------------------------

// Path to our application's resources directory
static NSString *resourcePath = nil;


// Lock to ensure only one thread at a time goes through ExifTags at any one time
static NSLock	*mapExifTools = nil;

// Lock to ensure only one thread at a time uses the Epeg library
static NSLock	*lockEpeg = nil;

//------------------------------------------------------------------------------
void getBoolFromPreferences(NSUserDefaults* userPrefs, NSString* key, BOOL *pValue)
{
	if (nil != [userPrefs objectForKey: key])
	{
		*pValue = [userPrefs boolForKey: key];
	}
}

//------------------------------------------------------------------------------
int getIntFromPreferences(NSUserDefaults* userPrefs, NSString *key, int defValue)
{
	if (nil != [userPrefs objectForKey: key])
	{
		return [userPrefs integerForKey: key];
	}

	return defValue;
}

//------------------------------------------------------------------------------
NSString* getStringFromPreferences(NSUserDefaults* userPrefs, NSString *key, NSString *defValue)
{
	NSString *val = [userPrefs stringForKey: key];
	if (nil != val)
	{
		return val;
	}

	return defValue;
}

//------------------------------------------------------------------------------
NSColor* getColorFromPreferences(NSUserDefaults* userPrefs, NSString *key, NSColor *defValue)
{
	NSData *data = [userPrefs dataForKey: key];
	if (nil != data)
	{
		return [NSUnarchiver unarchiveObjectWithData: data];
	}

	return defValue;
}

//------------------------------------------------------------------------------
void putColorInPreferences(NSUserDefaults* userPrefs, NSString *key, NSColor *value)
{
	[userPrefs setObject: [NSArchiver archivedDataWithRootObject: value]  forKey: key];
}

//------------------------------------------------------------------------------
int compareThumbDataNames(id obj1, id obj2, void *context)
{
	return comparePaths([obj1 name], [obj2 name], context);
}

//------------------------------------------------------------------------------
int comparePaths(id obj1, id obj2, void *context)
{
    // we assume the strings are equal until proven otherwise
    NSComparisonResult result = NSOrderedSame;

    // create a scanner for each string
    NSScanner* scanner1 = [[NSScanner alloc] initWithString: obj1];
    NSScanner* scanner2 = [[NSScanner alloc] initWithString: obj2];

    // create a character set of the decimal digits (0-9) to find numbers
    NSCharacterSet* numberSet = [NSCharacterSet decimalDigitCharacterSet];

    NSString* sub1;	// used to hold the substrings from the scanners
    NSString* sub2;

    BOOL gotSub1;	// did the scanners return a valid substring?
    BOOL gotSub2;

    BOOL isAtEnd1 = [scanner1 isAtEnd];		// has the scanner finished
    BOOL isAtEnd2 = [scanner2 isAtEnd];

    // keep looking until the strings are different, or we reach the end of a string
    while ((result == NSOrderedSame) && !isAtEnd1 && !isAtEnd2)
    {
        // first, we look for a text substring that does not contain a number
        gotSub1 = [scanner1 scanUpToCharactersFromSet:numberSet intoString:&sub1];
        gotSub2 = [scanner2 scanUpToCharactersFromSet:numberSet intoString:&sub2];
        
        if (gotSub1 && gotSub2)			// abc123 = abc456
            result = [sub1 caseInsensitiveCompare:sub2];
        else if (!gotSub1 && gotSub2)		// 123abc < abc123 
            result = NSOrderedAscending;
        else if (gotSub1 && !gotSub2)		// abc123 > 456def
            result = NSOrderedDescending;

        // see if either string is at the end
        isAtEnd1 = [scanner1 isAtEnd];
        isAtEnd2 = [scanner2 isAtEnd];

        // if the string is still equal, we look for a number substring
        if ((result == NSOrderedSame) && !isAtEnd1 && !isAtEnd2)
        {
            long long number1;
            long long number2;

            gotSub1 = [scanner1 scanLongLong: &number1];
            gotSub2 = [scanner2 scanLongLong: &number2];

            if (gotSub1 && gotSub2)
            {
                if (number1 < number2)
                    result = NSOrderedAscending;
                else if (number1 > number2)
                    result = NSOrderedDescending;
            }
            else if (!gotSub1 && gotSub2)
                result = NSOrderedAscending;
            else if (gotSub1 && !gotSub2)
                result = NSOrderedDescending;

            isAtEnd1 = [scanner1 isAtEnd];
            isAtEnd2 = [scanner2 isAtEnd];
        }
    }

    // when we exit the while loop, at least one of the strings has been exhausted
    // if the strings are still considered equal and only one string has finished
    // then the finished string is the lower string (e.g. he/help)
    if (result == NSOrderedSame)
    {
        if (isAtEnd1 && !isAtEnd2)
            result = NSOrderedAscending;
        else if (!isAtEnd1 && isAtEnd2)
            result = NSOrderedDescending;
    }

    [scanner1 release];
    [scanner2 release];

    return result;
}

//------------------------------------------------------------------------------
int compareThumbDataDates(id obj1, id obj2, void *context)
{
	NSDate *myDate;
	NSDate *otherDate;

	if (nil == context)
	{
		myDate = [obj1 date];
		otherDate = [obj2 date];
	}
	else
	{
		myDate = [obj1 fileDate];
		otherDate = [obj2 fileDate];
	}

	NSComparisonResult ret = [myDate compare: otherDate];
	if (NSOrderedSame == ret)
	{
		return compareThumbDataNames(obj1, obj2, context);
	}
	else
	{
		return ret;
	}
}

//------------------------------------------------------------------------------
int compareThumbDataTypes(id obj1, id obj2, void *context)
{
	int myType = (int)[obj1 mediaType];
	int otherType = (int)[obj2 mediaType];

	int diff = myType - otherType;
	if (0 == diff)
	{
		return compareThumbDataNames(obj1, obj2, context);
	}
	else
	{
		return diff;
	}
}

//------------------------------------------------------------------------------
int compareThumbStates(id obj1, id obj2, void *context)
{
	int myState = [obj1 thumbState];
	int otherState = [obj2 thumbState];

	int diff = myState - otherState;
	if (0 == diff)
	{
		// Check that the dates are set properly, if not, put those up to the front.
		diff = [obj1 doDatesMatch] - [obj2 doDatesMatch];
		if (0 == diff)
		{
			return compareThumbDataNames(obj1, obj2, context);
		}
	}

	return diff;
}

//------------------------------------------------------------------------------
int compareFileDates(id obj1, id obj2, void *context)
{
	NSString *file1 = obj1;
	NSString *file2 = obj2;
	if (nil != context)
	{
		file1 = [(NSString*)context stringByAppendingPathComponent: file1];
		file2 = [(NSString*)context stringByAppendingPathComponent: file2];
	}

	NSDate *date1 = exifOrFileDate(file1);
	NSDate *date2 = exifOrFileDate(file2);
	NSComparisonResult ret = [date1 compare: date2];
	if (NSOrderedSame == ret)
	{
		return comparePaths(obj1, obj2, context);
	}
	else
	{
		return ret;
	}
}

//------------------------------------------------------------------------------
NSDate *exifOrFileDate(NSString *filename)
{
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSDictionary *attrs = [fileManager fileAttributesAtPath: filename traverseLink: NO];
	NSDate *exifDateTime = [attrs objectForKey:NSFileModificationDate];

	return exifDateTime;
}

//------------------------------------------------------------------------------
NSString* formatLength(UInt64 length)
{
	char buffer[512];
	if (0 == length)
	{
		sprintf(buffer, "Zero KB");
	}
	else
	{
		int units = 0;
		double dblLength = (double) length;
		while (dblLength > 1024.0 && units < 3)
		{
			++units;
			dblLength /= 1024.0;
		}

		char *format = "%.01f %s";
		char *unitName;
		switch (units)
		{
			// bytes
			case 0:
				format = "%0.0f %s";
				unitName = "bytes";
				break;

			// kilobytes
			case 1:
				format = "%0.0f %s";
				unitName = "KB";
				break;

			// megabytes
			case 2:
				unitName = "MB";
				break;

			// gigabytes (and everything above it)
			case 3:
			default:
				unitName = "GB";
				break;
		}

		sprintf(buffer, format, dblLength, unitName);
	}

	return [NSString stringWithCString: buffer];
}

//------------------------------------------------------------------------------
/**
  *	The returned value will automatically be released.
  */
NSData* generateThumbData(NSString *filename, int maxWidth, int maxHeight)
{
	// The Epeg library uses static data; we need to limit access to a single thread
	// at a time
	if (nil == lockEpeg)
	{
		lockEpeg = [[NSLock alloc] init];
	}


	Epeg_Image *epeg = epeg_file_open([filename cStringUsingEncoding:NSUTF8StringEncoding]);
	if (nil == epeg)
	{
		return nil;
	}


	[lockEpeg lock];


	int width, height;
	epeg_size_get(epeg, &width, &height);

	int sW, sH;
	if (width > height)
	{
		sW = maxWidth;
		sH = (int) ((float) height * (float) sW / (float) width);
	}
	else
	{
		sH = maxHeight;
		sW = (int)((float) width * (float) sH / (float) height);
	}

	epeg_decode_size_set(epeg, sW, sH);

	unsigned char *epData = NULL;
	int epSize = 0;
	epeg_memory_output_set(epeg, &epData, &epSize);
	// I see crashes in epeg_encode if locks aren't used.
	int encoded = epeg_encode(epeg);
	epeg_close(epeg);

	[lockEpeg unlock];

	if (0 != encoded)
	{
		return nil;
	}

	NSData *imageData = [NSData dataWithBytes: epData  length: epSize];
	free(epData);

	return imageData;
}

//------------------------------------------------------------------------------
/**
  *	Caller is responsible for calling release on the returned image.
  */
NSImage* generateThumbnail(NSString *filename, int maxWidth, int maxHeight)
{
	NSData *imageData = generateThumbData(filename, maxWidth, maxHeight);
	if (nil == imageData)
	{
		// 2006-09-25; I saw deadlocks in this code on JPEGs from a camera (this
		// code was executed because of a bug since fixed).
		NSData *data = [NSData dataWithContentsOfFile: filename];
		NSImage *thumb = [[NSImage alloc] initWithData: data];
		if (nil == thumb)
		{
			// Perhaps this is a movie supported by QuickTime?
			thumb = imageFromMovieFile(filename);
		}

		if (nil != thumb)
		{
			// Scale the image proportionally, if needed
			[thumb setScalesWhenResized: YES];

			NSSize size = [thumb size];
			if (size.width > maxWidth || size.height > maxHeight)
			{
				if (size.width >= size.height)
				{
					size.height = size.height * maxWidth / size.width;
					size.width = maxWidth;
				}
				else
				{
					size.width = size.width * maxWidth / size.height;
					size.height = maxHeight;
				}
			}
			NSImage *sizedThumb = [[NSImage alloc] initWithSize: size];

			[sizedThumb lockFocus];
			[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
			[thumb setSize: size];
			[thumb compositeToPoint: NSZeroPoint operation: NSCompositeCopy];
			[sizedThumb unlockFocus];
			[thumb release];

			return sizedThumb;
		}
		else
		{
			return thumb;
		}
	}
	else
	{
		NSImage *thumb = [[NSImage alloc] initWithData: imageData];
		return thumb;
	}
}

//------------------------------------------------------------------------------
/**
 *	Caller is responsible for releasing the returned pThumbnail
 */
void getExifData(NSString *filename, NSMutableArray **pList, NSDate **pDate, NSImage **pThumbnail)
{
	if (nil == mapExifTools)
	{
		mapExifTools = [[NSLock alloc] init];
	}

	// The ExifTags code we call uses some static variables - we need to control
	// access such that only one thread at a time is in there.
	[mapExifTools lock];


	FILE *file = fopen([filename cStringUsingEncoding:NSUTF8StringEncoding], "r");
	if (NULL != file)
	{
		int mark;
		unsigned int len;
		int first = 1;
		while (jpegscan(file, &mark, &len, first))
		{
			first = 0;

			// Skip markers 'exifparse' can't handle.
			if (JPEG_M_APP1 != mark)
			{
				if (fseek(file, len, SEEK_CUR))
				{
					NSLog(@"fseek to %d failed for %@", len, filename);
					break;
				}

				continue;
			}

			unsigned char *buffer = malloc(len);
			if (NULL == buffer)
			{
				NSLog(@"malloc(%d) failed for %@", len, filename);
				break;
			}

			BOOL exitLoop = NO;
			int cRead = fread(buffer, 1, len, file);
			if (cRead == len)
			{
				struct exiftags *tags = exifparse(buffer, len);
				if (NULL != tags && tags->props)
				{
					int thumbOffset = -1;
					int thumbLength = -1;

					struct exifprop *prop = tags->props;
					while (NULL != prop)
					{
						if (ED_IMG == prop->type)
						{
							if (0x0201 == prop->tag)
							{
								thumbOffset = prop->value;
							}
							else
							if (0x0202 == prop->tag)
							{
								thumbLength = prop->value;
							}
						}

						if (nil != pList)
						{
							const char *name = prop->descr;
							if (NULL == name)
							{
								name = prop->name;
							}

							char valueBuffer[256];
							const char *value = prop->str;
							if (NULL == value)
							{
								sprintf(valueBuffer, "%d", prop->value);
								value = valueBuffer;
							}

							ExifItem *eItem = [[ExifItem alloc] initExif
								:prop->type
								tag:prop->tag
								name: [NSString stringWithCString: name]
								value: [NSString stringWithCString: value]];
							[*pList addObject: eItem];
							[eItem release];
						}

						if (nil != pDate
							&& ED_CAM == prop->type && EXIF_T_DATETIMEORIG == prop->tag
							&& nil != prop->str)
						{
							struct tm dateTime;
							memset(&dateTime, 0, sizeof (struct tm));
							sscanf(prop->str,
									"%d:%d:%d %d:%d:%d", 
									&dateTime.tm_year, &dateTime.tm_mon, &dateTime.tm_mday, 
									&dateTime.tm_hour, &dateTime.tm_min, &dateTime.tm_sec);
							dateTime.tm_year -= 1900;
							dateTime.tm_mon -= 1;
							dateTime.tm_isdst = -1;

							time_t exifTime = 0;
							exifTime = mktime(&dateTime);

							*pDate = [NSDate dateWithTimeIntervalSince1970: exifTime];
						}

						prop = prop->next;
					}

					if (nil != pThumbnail && thumbOffset > 0 && thumbLength > 0)
					{
						if (thumbOffset + thumbLength < len)
						{
							// offset of 6 was determined empirically - I hope it's a fixed
							// sized header (that I don't know about)
							unsigned char *tbuf = buffer + thumbOffset + 6;
							NSData *imageData = [NSData dataWithBytesNoCopy
								: tbuf  
								length: thumbLength
								freeWhenDone: NO];
							*pThumbnail = [[NSImage alloc] initWithData: imageData];
						}
					}
				}

				exiffree(tags);
				tags = NULL;
			}
			else
			{
				NSLog(
						@"Read wrong number of bytes; expected %d; actual %d for %@", 
						len, 
						cRead, 
						filename);
				exitLoop = YES;
			}

			free(buffer);
			buffer = NULL;

			if (YES == exitLoop)
			{
				break;
			}
		}

		fclose(file);
		file = NULL;
	}
	else
	{
		NSLog(@"Unable to open file: %@", filename);
	}

	[mapExifTools unlock];
}

#define FORMAT_EXIF_TIME	"%Y:%m:%d %H:%M:%S"
#define LEN_EXIF_TIME		19		// %Y is 4 digits

//------------------------------------------------------------------------------
BOOL adjustDate(char *dst, char *src, long delta, NSDate **pNewDate)
{
	struct tm dateTime;
	memset(&dateTime, 0, sizeof (struct tm));
	int nFields = sscanf(src,
			"%d:%d:%d %d:%d:%d", 
			&dateTime.tm_year, &dateTime.tm_mon, &dateTime.tm_mday, 
			&dateTime.tm_hour, &dateTime.tm_min, &dateTime.tm_sec);
	if (6 != nFields)
	{
		return NO;
	}

	dateTime.tm_year -= 1900;
	dateTime.tm_mon -= 1;
	dateTime.tm_isdst = -1;

	time_t exifTime = 0;
	exifTime = timegm(&dateTime);
	exifTime += delta;

	struct tm newDateTime;
	memset(&newDateTime, 0, sizeof (struct tm));
	gmtime_r(&exifTime, &newDateTime);
	if (LEN_EXIF_TIME != strftime(dst, LEN_EXIF_TIME + 1, FORMAT_EXIF_TIME, &newDateTime))
	{
		return NO;
	}

	if (nil != pNewDate)
	{
		memset(&dateTime, 0, sizeof (struct tm));
		sscanf(dst,
				"%d:%d:%d %d:%d:%d", 
				&dateTime.tm_year, &dateTime.tm_mon, &dateTime.tm_mday, 
				&dateTime.tm_hour, &dateTime.tm_min, &dateTime.tm_sec);

		dateTime.tm_year -= 1900;
		dateTime.tm_mon -= 1;
		dateTime.tm_isdst = -1;
		exifTime = mktime(&dateTime);

		*pNewDate = [NSDate dateWithTimeIntervalSince1970: exifTime];
	}

	return YES;
}

//------------------------------------------------------------------------------
BOOL writeExifDate(
	const char *newDate,
	NSString *filename, FILE *file, int pos, 
	struct exiftags *et, struct exifprop *prop, unsigned char *exifBuffer)
{
	int savedPos = ftell(file);

	int datePos = pos + (et->md.btiff - exifBuffer) + prop->value;
	if (0 != fseek(file, datePos, SEEK_SET))
	{
		NSLog(@"writeExifDate; first fseek failed for %@, %d", filename, errno);
		return  NO;
	}

	if (1 != fwrite(newDate, LEN_EXIF_TIME + 1, 1, file))
	{
		NSLog(@"writeExifDate; fwrite failed for %@, %d", filename, errno);
		return NO;
	}

	if (0 != fseek(file, savedPos, SEEK_SET))
	{
		NSLog(@"writeExifDate; second fseek failed for %@, %d", filename, errno);
		return  NO;
	}

	return YES;
}

//------------------------------------------------------------------------------
BOOL setExifDates(NSString *filename, long delta, NSDate **pNewDate)
{
	if (0 == delta)
	{
		return YES;
	}

	BOOL failure = NO;
	int dateProperties = 0;
	int updatedDates = 0;
	FILE *file = fopen([filename cStringUsingEncoding:NSUTF8StringEncoding], "r+");
	if (NULL != file)
	{
		int mark;
		unsigned int len;
		int first = 1;
		while (jpegscan(file, &mark, &len, first))
		{
			first = 0;

			// Skip markers 'exifscan' can't handle.
			if (JPEG_M_APP1 != mark)
			{
				if (fseek(file, len, SEEK_CUR))
				{
					NSLog(@"fseek to %d failed for %@", len, filename);
					failure = YES;
					break;
				}

				continue;
			}

			unsigned char *buffer = malloc(len);
			if (NULL == buffer)
			{
				NSLog(@"malloc(%d) failed for %@", len, filename);
				failure = YES;
				break;
			}

			BOOL exitLoop = NO;
			long fpos = ftell(file);
			int cRead = fread(buffer, 1, len, file);
			if (cRead == len)
			{
				struct exiftags *et = exifscan(buffer, len, FALSE);
				if (NULL != et && NULL != et->props)
				{
					char newDate[LEN_EXIF_TIME + 1];
					struct exifprop *prop = findprop(et->props, tags, EXIF_T_DATETIME);
					if (NO == failure && NULL != prop && NULL != prop->str)
					{
						++dateProperties;
						if (YES == adjustDate(newDate, prop->str, delta, nil))
						{
							if (YES == writeExifDate(
									newDate, filename, file, fpos,
									et, prop, buffer))
							{
								++updatedDates;
							}
							else
							{
								failure = YES;
							}
						}
					}

					prop = findprop(et->props, tags, EXIF_T_DATETIMEORIG);
					if (NO == failure && NULL != prop && NULL != prop->str)
					{
						++dateProperties;
						if (YES == adjustDate(newDate, prop->str, delta, pNewDate))
						{
							if (YES == writeExifDate(
									newDate, filename, file, fpos,
									et, prop, buffer))
							{
								++updatedDates;
							}
							else
							{
								failure = YES;
							}
						}
					}

					prop = findprop(et->props, tags, EXIF_T_DATETIMEDIGI);
					if (NO == failure && NULL != prop && NULL != prop->str)
					{
						++dateProperties;
						if (YES == adjustDate(newDate, prop->str, delta, nil))
						{
							if (YES == writeExifDate(
									newDate, filename, file, fpos,
									et, prop, buffer))
							{
								++updatedDates;
							}
							else
							{
								failure = YES;
							}
						}
					}
				}
				else
				{
					NSLog(@"exifscan return nil for %@", filename);
					exitLoop = YES;
				}

				exiffree(et);
			}
			else
			{
				NSLog(
						@"Read wrong number of bytes; expected %d; actual %d for %@", 
						len, 
						cRead, 
						filename);
				exitLoop = YES;
			}

			free(buffer);
			buffer = NULL;

			if (YES == exitLoop)
			{
				break;
			}
		}

		fclose(file);
		file = NULL;

		if (NO == failure && 0 == dateProperties)
		{
			// This file doesn't have EXIF date properties, but they want a new date. Calculate 
			// the new date from the file creation date.
			NSDictionary *attrs = [[NSFileManager defaultManager]
					fileAttributesAtPath:filename 
					traverseLink: NO];
			NSDate *createDate = [attrs fileCreationDate];
			*pNewDate = [[NSDate alloc] initWithTimeInterval: delta  sinceDate: createDate];
		}
	}

	return NO == failure && dateProperties == updatedDates;
}

//------------------------------------------------------------------------------
int runExiftran(NSString *filename, NSString *option)
{
	if (nil == resourcePath)
	{
		resourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"exiftran"];
		NSFileManager *manager = [NSFileManager defaultManager];
		if (NO == [manager fileExistsAtPath:resourcePath  isDirectory:nil])
		{
			return -1;
		}

		[resourcePath retain];
	}

	NSTask *task = [NSTask
		launchedTaskWithLaunchPath: resourcePath
		arguments: [NSArray arrayWithObjects:@"-ip", option, filename, nil]];
	[task waitUntilExit];
	return [task terminationStatus];
}

//------------------------------------------------------------------------------
int rotate90(NSString *filename)
{
	return runExiftran(filename, @"-9");
}

//------------------------------------------------------------------------------
int rotate270(NSString *filename)
{
	return runExiftran(filename, @"-2");
}

//------------------------------------------------------------------------------
int generateExifThumbnail(NSString *filename)
{
	return runExiftran(filename, @"-g");
}

//----------------------------------------------------------------------------
NSImage* imageFromMovieFile(NSString* filename)
{
	Movie movie = NULL;


	FSRef fileRef;
	ComponentResult err = FSPathMakeRef((const UInt8 *)[filename cStringUsingEncoding:NSUTF8StringEncoding], &fileRef, NULL);
	if (noErr != err)
	{
		NSLog(@"imageFromMovieFile; FSPathMakeRef(%@) failed: %d", filename, err);
		return nil;
	}

	FSSpec fileSpec;
	err = FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec, NULL);
	if (noErr != err)
	{
		NSLog(@"imageFromMovieFile; FSGetCatalogInfo(%@) failed: %d", filename, err);
		return nil;
	}

	short resRefNum;
	err = OpenMovieFile(&fileSpec, &resRefNum, fsRdPerm);
	if (noErr != err)
	{
		NSLog(@"imageFromMovieFile; OpenMovieFile(%@) failed: %d", filename, err);
		return nil;
	}

	short resId = 0;
	err = NewMovieFromFile(
			&movie,
			resRefNum,
			&resId,
			NULL,
			newMovieDontInteractWithUser | newMovieDontAskUnresolvedDataRefs | newMovieActive, 
			NULL);


	NSImage *thumb = nil;
	if (noErr == err)
	{
		thumb = imageFromMovie(movie, 0);
		[thumb retain];
	}
	else
	{
		if (componentNotThreadSafeErr != err)
		{
			NSLog(@"imageFromMovieFile; NewMovieFromFile(%@) failed: %d", filename, err);
		}
	}

	CloseMovieFile(resRefNum);


	if (NULL != movie)
	{
		DisposeMovie(movie);
		movie = NULL;
	}
/*
	if (NULL != hDataRef)
	{
		DisposeHandle(hDataRef);
		hDataRef = NULL;
	}
*/
	return thumb;
}


// ---------------------------------------
//  imageFromMovie:atTime:
// ---------------------------------------
//
NSImage* imageFromMovie(Movie movie, TimeValue time)
{
    // Pin the time to legal values.
    TimeValue duration = GetMovieDuration(movie);
    if (time > duration)
        time = duration;
    if (time < 0)
		time = 0;
    
    // Create an offscreen GWorld for the movie to draw into.
    GWorldPtr gworld = gworldForMovie(movie);
    
    // Set the GWorld for the movie.
    GDHandle oldDevice;
    CGrafPtr oldPort;
    GetMovieGWorld(movie,&oldPort,&oldDevice);
    SetMovieGWorld(movie,gworld,GetGWorldDevice(gworld));
    
    // Advance the movie to the appropriate time value.
    SetMovieTimeValue(movie, time);
    
    // Draw the movie.
    UpdateMovie(movie);
    MoviesTask(movie, 0);

    // Create an NSImage from the GWorld.
    NSImage *image = imageFromGWorld(gworld);

    // Restore the previous GWorld, then dispose the one we allocated.
    SetMovieGWorld(movie, oldPort, oldDevice);
    DisposeGWorld(gworld);

    return image;
}



// ---------------------------------------
// gworldForMovie:
// ---------------------------------------
//  Get the bounding rectangle of the Movie the create a 32-bit GWorld
//  with those dimensions.
//  This GWorld will be used for rendering Movie frames into.

GWorldPtr gworldForMovie(Movie movie)
{
    Rect        srcRect;
    GWorldPtr   newGWorld = NULL;
    CGrafPtr    savedPort;
    GDHandle    savedDevice;
    
    OSErr err = noErr;
    GetGWorld(&savedPort, &savedDevice);

    GetMovieBox(movie,&srcRect);
	OffsetRect(&srcRect, -srcRect.left, -srcRect.top);

    err = NewGWorld(&newGWorld,
                    k32ARGBPixelFormat,
                    &srcRect,
                    NULL,
                    NULL,
                    0);
    if (err == noErr)
    {
        if (LockPixels(GetGWorldPixMap(newGWorld)))
        {
            Rect        portRect;
            RGBColor    theBlackColor   = { 0, 0, 0 };
            RGBColor    theWhiteColor   = { 65535, 65535, 65535 };

            SetGWorld(newGWorld, NULL);
            GetPortBounds(newGWorld, &portRect);
            RGBBackColor(&theBlackColor);
            RGBForeColor(&theWhiteColor);
            EraseRect(&portRect);
            
            UnlockPixels(GetGWorldPixMap(newGWorld));
        }
    }

    SetGWorld(savedPort, savedDevice);
//    NSAssert(newGWorld != NULL, @"NULL gworld");
    return newGWorld;
}


// ---------------------------------------
// imageFromGWorld:
// ---------------------------------------
// Convert contents of a gworld to an NSImage 
//
NSImage*  imageFromGWorld(GWorldPtr gWorldPtr)
{
    PixMapHandle        pixMapHandle = NULL;
    Ptr                 pixBaseAddr = nil;
    NSBitmapImageRep    *imageRep = nil;
    NSImage             *image = nil;

//    NSAssert(gWorldPtr != nil, @"nil gWorldPtr");

    // Lock the pixels
    pixMapHandle = GetGWorldPixMap(gWorldPtr);
    if (pixMapHandle)
    {
        Rect        portRect;
        unsigned    portWidth, portHeight;
        int         bitsPerSample, samplesPerPixel;
        BOOL        hasAlpha, isPlanar;
        int         destRowBytes;

		LockPixels(pixMapHandle);

        GetPortBounds(gWorldPtr, &portRect);
        portWidth = (portRect.right - portRect.left);
        portHeight = (portRect.bottom - portRect.top);

        bitsPerSample   = 8;
        samplesPerPixel = 4;
        hasAlpha        = YES;
        isPlanar        = NO;
        destRowBytes    = portWidth * samplesPerPixel;
        imageRep        = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL 
                                                                pixelsWide:portWidth 
                                                                pixelsHigh:portHeight 
                                                            bitsPerSample:bitsPerSample 
                                                        samplesPerPixel:samplesPerPixel 
                                                                hasAlpha:hasAlpha 
                                                                isPlanar:NO
                                                          colorSpaceName:NSDeviceRGBColorSpace 
                                                             bytesPerRow:destRowBytes 
                                                            bitsPerPixel:0];
        if (imageRep)
        {
            unsigned char *theData;
            int     pixmapRowBytes;
            int     rowByte,rowIndex;

            theData = [imageRep bitmapData];
        
            pixBaseAddr = GetPixBaseAddr(pixMapHandle);
            if (pixBaseAddr)
            {
                pixmapRowBytes = GetPixRowBytes(pixMapHandle);
            
                for (rowIndex=0; rowIndex< portHeight; rowIndex++)
                {
                    unsigned char *dst = theData + rowIndex * destRowBytes;
                    unsigned char *src = (void *)pixBaseAddr + rowIndex * pixmapRowBytes;
                    unsigned char a,r,g,b;
                    
                    for (rowByte = 0; rowByte < portWidth; rowByte++)
                    {
                        a = *src++;     // get source Alpha component
                        r = *src++;     // get source Red component
                        g = *src++;     // get source Green component
                        b = *src++;     // get source Blue component  
            
                        *dst++ = r;     // set dest. Alpha component
                        *dst++ = g;     // set dest. Red component
                        *dst++ = b;     // set dest. Green component
                        *dst++ = a;     // set dest. Blue component  
                    }
                }
            
                image = [[[NSImage alloc] initWithSize:NSMakeSize(portWidth, portHeight)] autorelease];
                if (image)
                {
                    [image addRepresentation:imageRep];
                    [imageRep release];
                }
            }
        }
    }

/*
    NSAssert(pixMapHandle != NULL, @"null pixMapHandle");
    NSAssert(imageRep != nil, @"nil imageRep");
    NSAssert(pixBaseAddr != nil, @"nil pixBaseAddr");
    NSAssert(image != nil, @"nil image");
*/

    if (pixMapHandle)
    {
        UnlockPixels(pixMapHandle);
    }

    return image;
}


//--------------------------------------------------------------------------------------------------
static NSString* picturesFolder = nil;

NSString * GetPicturesFolder()
{
	if (nil == picturesFolder)
	{
		FSRef folderRef;
		unsigned char folder[1024];

		OSStatus err = FSFindFolder(
				kUserDomain, 
				kPictureDocumentsFolderType,
				kDontCreateFolder, 
				&folderRef);
		if (noErr != err)
		{
			NSLog(@"FSFindFolder (Pictures) failed with %d", err);
			return NSHomeDirectory();
		}

		FSRefMakePath(&folderRef, folder, 1024);
		picturesFolder = [NSString stringWithCString: (char*)folder];
		[picturesFolder retain];
	}

	return picturesFolder;
}

//--------------------------------------------------------------------------------------------------
void linearShadedColor(void *info, const float *in, float *out)
{
	float *colors = info;

	out[0] = colors[0] + *in * colors[8];
	out[1] = colors[1] + *in * colors[9];
	out[2] = colors[2] + *in * colors[10];
	out[3] = colors[3] + *in * colors[11];
}

//--------------------------------------------------------------------------------------------------
void linearGradientFill(NSColor* firstColor, NSColor* secondColor, CGRect rect)
{
	static const CGFunctionCallbacks callbacks = {0, &linearShadedColor, NULL};
	gradientFill(callbacks, firstColor, secondColor, rect);
}

//--------------------------------------------------------------------------------------------------
void gradientFill(
		CGFunctionCallbacks callbacks,
		NSColor* firstColor,
		NSColor* secondColor,
		CGRect rect)
{
	CGColorSpaceRef colorspace;
	CGShadingRef shading;
	CGPoint startPoint = {0, 0};
	CGPoint endPoint = {0, 0};
	CGFunctionRef function;
	float colors[12];

	CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

	NSColor *deviceDependentFirstColor = [firstColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
	NSColor *deviceDependentSecondColor = [secondColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];

	colors[0] = [deviceDependentFirstColor redComponent];
	colors[1] = [deviceDependentFirstColor greenComponent];
	colors[2] = [deviceDependentFirstColor blueComponent];
	colors[3] = [deviceDependentFirstColor alphaComponent];

	colors[4] = [deviceDependentSecondColor redComponent];
	colors[5] = [deviceDependentSecondColor greenComponent];
	colors[6] = [deviceDependentSecondColor blueComponent];
	colors[7] = [deviceDependentSecondColor alphaComponent];

	colors[8] = (colors[4]-colors[0]);
	colors[9] = (colors[5]-colors[1]);
	colors[10] = (colors[6]-colors[2]);
	colors[11] = (colors[7]-colors[3]);

	colorspace = CGColorSpaceCreateDeviceRGB();
	size_t components = 1 + CGColorSpaceGetNumberOfComponents(colorspace);
	static const float  domain[2] = {0.0, 1.0};
	static const float  range[10] = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
	function = CGFunctionCreate(colors, 1, domain, components, range, &callbacks);

	startPoint.x = CGRectGetMinX(rect);
	startPoint.y = CGRectGetMinY(rect);
	endPoint.x = CGRectGetMinX(rect);
	endPoint.y = CGRectGetMaxY(rect);

	shading = CGShadingCreateAxial(colorspace, startPoint, endPoint, function, NO, NO);
	CGContextClip(context);
	CGContextDrawShading(context, shading);

	CGShadingRelease(shading);
	CGFunctionRelease(function);
	CGColorSpaceRelease(colorspace);
}
